Online election¶
Time: CTOR:O(N), Query:O(LogN); Space: O(N); medium
In an election, the i-th vote was cast for persons[i] at time times[i].
Now, we would like to implement the following query function:
TopVotedCandidate.q(int t)
will return the number of the person that was leading the election at time t.
Votes cast at time t will count towards our query.
In the case of a tie, the most recent vote (among tied candidates) wins.
Example 1:
Input/Output:
persons = [0,1,1,0,0,1,0]
times = [0, 5, 10, 15, 20, 25, 30]
s = TopVotedCandidate(persons, times)
s.q(3) -> 0
s.q(12) -> 1
s.q(25) -> 1
s.q(15) -> 0
s.q(24) -> 0
s.q(8) -> 1
Explanation:
At time 3, the votes are [0], and 0 is leading.
At time 12, the votes are [0,1,1], and 1 is leading.
At time 25, the votes are [0,1,1,0,0,1], and 1 is leading (as ties go to the most recent vote.)
This continues for 3 more queries at time 15, 24, and 8.
Constraints:
1 <= len(persons), len(times) <= 5000
0 <= persons[i] <= len(persons)
times is a strictly increasing array with all elements in [0, 10^9].
TopVotedCandidate.q is called at most 10000 times per test case.
TopVotedCandidate.q(int t) is always called with t >= times[0].
1. Binary Search (List of Lists) [O(N+QLogN), O(N)]¶
Intuition and Algorithm
We can store the votes in a list A of lists of votes. Each vote has a person and a timestamp, and A[count] is a list of the count-th votes received for that person.
Then, A[i][0] and A[i] are monotone increasing, so we can binary search on them to find the most recent vote by time.
[1]:
import bisect
import collections
class TopVotedCandidate1(object):
"""
Time: O(N+QLogN), where N is the number of votes, and Q is the number of queries.
Space: O(N)
"""
def __init__(self, persons, times):
"""
:type persons: List[int]
:type times: List[int]
"""
self.A = []
self.count = collections.Counter()
for p, t in zip(persons, times):
self.count[p] = c = self.count[p] + 1
while len(self.A) <= c:
self.A.append([])
self.A[c].append((t, p))
def q(self, t):
lo, hi = 1, len(self.A)
while lo < hi:
mi = (lo + hi) // 2
if self.A[mi][0][0] <= t:
lo = mi + 1
else:
hi = mi
i = lo - 1
j = bisect.bisect(self.A[i], (t, float('inf')))
return self.A[i][j-1][1]
[2]:
persons = [0, 1, 1, 0, 0, 1, 0]
times = [0, 5, 10, 15, 20, 25, 30]
s = TopVotedCandidate1(persons, times)
assert(s.q(3)) == 0
assert(s.q(12)) == 1
assert(s.q(25)) == 1
assert(s.q(15)) == 0
assert(s.q(24)) == 0
assert(s.q(8)) == 1
2. Binary Search (Precomputed Answer) [O(N + QLogN), O(N)]¶
Intuition and Algorithm
As the votes come in, we can remember every event (winner, time) when the winner changes. After, we have a sorted list of these events that we can binary search for the answer.
[3]:
import collections
import bisect
class TopVotedCandidate2(object):
"""
Time: O(N+QLogN), where N is the number of votes, and Q is the number of queries.
Space: O(N)
"""
def __init__(self, persons, times):
"""
:type persons: List[int]
:type times: List[int]
"""
lead = -1
self.A, count = [], collections.defaultdict(int)
for t, p in zip(times, persons):
count[p] += 1
if count[p] >= count[lead]:
lead = p
self.A.append((t, lead))
def q(self, t):
"""
:type t: int
:rtype: int
"""
i = bisect.bisect(self.A, (t, float('inf')), 1)
return self.A[i-1][1]
[4]:
persons = [0, 1, 1, 0, 0, 1, 0]
times = [0, 5, 10, 15, 20, 25, 30]
s = TopVotedCandidate2(persons, times)
assert(s.q(3)) == 0
assert(s.q(12)) == 1
assert(s.q(25)) == 1
assert(s.q(15)) == 0
assert(s.q(24)) == 0
assert(s.q(8)) == 1